package com.marketlive.system.config;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import org.apache.commons.lang.builder.ToStringBuilder;
import org.marketlive.system.config.IConfigurationBranch;
import org.marketlive.system.config.IConfigurationLeaf;


import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Creates a <code>ConfigurationBranch</code> with the given name. Returns an array of sub-branches 
 * attached to this branch. 
 * 
 */
public class ConfigurationBranch extends ConfigurationNode implements IConfigurationBranch {

    /**
     * Symbol used to separate nodes in a configuration name.  This symbol cannot
     * appear in configuration names.
     */
    public static final String NODE_SEPARATOR = ".";
    /**
     * Symbol used to "escape" the separator character when the character must be used literally.
     */
    public static final String NODE_SEPARATOR_ESCAPED = "\\.";

    /**
     * Logger
     */
    private static Log log = LogFactory.getLog(ConfigurationBranch.class);
    /** Cached array of branches. Lazily created. */
    private IConfigurationBranch[] subBranches = null;
    /** Managed list of branches. */
    private Map<String, IConfigurationBranch> subBranchMap = new ConcurrentHashMap<String, IConfigurationBranch>();
    /** Cached array of leaves.  Lazily created. */
    private IConfigurationLeaf[] leaves = null;
    /** Managed list of leaves. */
    private Map<String,IConfigurationLeaf> leafMap = new ConcurrentHashMap<String, IConfigurationLeaf>();

    /**
     * Creates a branch with the given name.
     *
     * @param name the name to use for the new branch
     */
    public ConfigurationBranch(String name) {
        super(name);
    }

    /**
     * @inheritDoc
     */
    public IConfigurationBranch getBranch(String name) throws IllegalArgumentException {
        return (IConfigurationBranch) subBranchMap.get(name);
    }

    /**
     * @inheritDoc
     */
    public IConfigurationBranch[] getBranches() {
        if (subBranches == null) {
            subBranches = (IConfigurationBranch[]) subBranchMap.values().toArray(new IConfigurationBranch[0]);
        }
        return subBranches;
    }

    /**
     * Adds the given <code>IConfigurationBranch</code> to this branch, if possible.
     * 
     * @param newBranch the <code>IConfigurationBranch</code> to add
     * @throws IllegalArgumentException
     */
    public void addBranch(IConfigurationBranch newBranch) throws IllegalArgumentException {
        if (subBranchMap.get(newBranch.getName()) != null) {
            throw new IllegalArgumentException("Branch with name: " + newBranch.getName() + " already exists on this branch: " + this);
        }
        subBranchMap.put(newBranch.getName(), newBranch);

        // force a regeneration of the array on the next request.
        subBranches = null;
    }

    /**
     * Determines if at least one sub-branch of the given branch exists.
     * @param name of the branch to test
     * @return true if at least one sub-branch exists; false otherwise
     */
    public boolean hasBranch(String name) {
        return (subBranchMap.get(name) != null);
    }

    /**
     * @inheritDoc
     */
    public IConfigurationLeaf getLeaf(String name) throws IllegalArgumentException {
        // seperate the branch path from the leaf name.
        String[] nameElements = name.split(NODE_SEPARATOR_ESCAPED);
        return getLeaf(nameElements, 0);
        }

    /**
     * Returns the <code>IConfigurationLeaf</code> for the given branch in the 
     * provided <code>String</code> array.
     * 
     * @param nameElements the <code>String</code> array specifying the branches
     * @param index the index of the branch whose leaf to return
     * @return the <code>IConfigurationLeaf</code> for the given branch
     * @throws IllegalArgumentException if branch does not exist
     */
    public IConfigurationLeaf getLeaf(String[] nameElements, int index) throws IllegalArgumentException {
        if (index == nameElements.length - 1) {
            //log.debug( ConfigurationToString.toStringBuilder(new StringBuilder()))
            return getChildLeaf(nameElements[index]);
        } else {
        	ConfigurationBranch cb = (ConfigurationBranch) getBranch(nameElements[index]);
            if (cb==null)
                throw new IllegalArgumentException( "No branch named '" + nameElements[index] + "' on branch: " + this );
            return cb.getLeaf(nameElements, index + 1);
    }
    }

    /**
     * @inheritDoc
     */
    public IConfigurationLeaf[] getLeaves() {
        if (leaves == null) {
            leaves = (IConfigurationLeaf[]) leafMap.values().toArray(new IConfigurationLeaf[0]);
        }
        return leaves;
    }

    /**
     * Adds a new <code>IConfigurationLeaf</code>, specified by newLeaf, to this branch
     * 
     * @param newLeaf the <code>IConfigurationLeaf</code> to add
     * @throws IllegalArgumentException if newLeaf is not properly specified
     */
    public void addLeaf(IConfigurationLeaf newLeaf) throws IllegalArgumentException {

        if (leafMap.get(newLeaf.getName()) != null) {
            log.info( "overriding value for leaf: " + newLeaf.getName());
            // throw new IllegalArgumentException("Leaf with name: " + newLeaf.getName() + " already exists on this branch: " + this);
        }
        leafMap.put(newLeaf.getName(), newLeaf);

        // force a regeneration of the array on the next request.
        leaves = null;
    }


    /**
     * Returns the <code>IConfigurationBranch</code> specified by path, optionally created
     * if necessary.
     * 
     * @param path the branch whose <code>IConfigurationBranch</code> to return
     * @param createIfRequired if true, optionally create if needed 
     * @return <code>IConfigurationBranch</code> for the given path
     */
    public IConfigurationBranch getBranch(String path, boolean createIfRequired) {
        log.debug("getBranch called with " + path);
        //System.out.println("getBranch called with " + path);
        
        String[] pathElements = path.split( NODE_SEPARATOR_ESCAPED );

        return getBranch(pathElements, 0, createIfRequired);
    }

    /**
     * Returns the <code>IConfigurationBranch</code> for the branch specified by the concatenation of
     * pathElements starting at startIndex, optionally created if necessary.
     * 
     * @param pathElements an array of pathelements to use to create the branch
     * @param startIndex the index of the first element in the elements array to start using
     * @param createIfRequired if true, optionally create if needed
     * @return the <code>IConfigurationBranch</code> found or optionally created for the elements specified
     */
    private IConfigurationBranch getBranch(String[] pathElements, int startIndex, boolean createIfRequired) {
        log.debug("getBranch called with " + ToStringBuilder.reflectionToString(pathElements) + " at index: " + startIndex);
        //System.out.println("getBranch called with " + ToStringBuilder.reflectionToString(pathElements) + " at index: " + startIndex);

        String branchName = pathElements[startIndex];
        ConfigurationBranch subBranch;

        if (!createIfRequired && !hasBranch(branchName)) {
            throw new IllegalArgumentException("No branch with path:" + ConfigurationNode.arrayToString(pathElements));
        }

        // get or create the subbranch
        if (hasBranch(branchName)) {
            subBranch = (ConfigurationBranch) getBranch(branchName);
        } else {
            subBranch = new ConfigurationBranch(branchName);
            this.addBranch(subBranch);
        }

        // if this is the last branch in the path return it
        if (pathElements.length == startIndex + 1) {
            return subBranch;
        } else {
            // get the rest of the path from the subbranch
            return subBranch.getBranch(pathElements, startIndex + 1, createIfRequired);
        }
    }

    /**
     * @inheritDoc
     */
    public String getAsString(String name) {
        return getLeaf(name).getAsString();
    }

    /**
     * Returns the leaf at the specified path.  The path is an array of branch names ending
     * in a leaf name.  The path is relative to this branch, starting in the array at the specified index.
     *
     * @param nameElements the array of elements comprising the path
     * @param index of the path element in nameElements to start with
     * @return the leaf name for the path specified by concatenating the path elements in the nameElements array
     * 
     */
    public String getAsString(String[] nameElements, int index) {
        return getLeaf(nameElements,index).getAsString();
        }


    /**
     * Adds to the List of Leaf names that are defined in this branch and its sub-branches.
     * The leaf names are optionally prefixed.
     * 
     * @param names a <code>List</code> of names to add 
     * @param prefix an optional prefix to prepend to the names before adding them
     */
    public void getLeafNames(final List names, String prefix) {
        String p = (prefix == null ? "" : prefix);

        // don't include the Root branch in the name
        String branchName = p + (isRoot() ? "" : (this.getName()+"."));

        IConfigurationLeaf[] leaves = getLeaves();
        for (int i=0;i<leaves.length;i++) {
            String fullName = branchName + leaves[i].getName();
            log.debug( "added: " + fullName);
            names.add(fullName);
        }

        IConfigurationBranch[] branches = (IConfigurationBranch[]) getBranches();
        for (int i=0;i<branches.length;i++) {
            ((ConfigurationBranch)branches[i]).getLeafNames(names, branchName);
        }
    }

    /**
     * Returns a formatted string containing the name of this branch.
     * 
     * @return a formatted string containing the name
     */
    
    public String toString() {
//        return ToStringBuilder.reflectionToString(this);
        return "[branch|" + this.getName() + "]";
    }

    /**
     * Returns the full path of this branch ending with the name.
     * @return a String containing the path and name of this branch
     */
    public String toStringFull() {
    	StringBuffer out = new StringBuffer(100);
    	ConfigurationToString.toStringBuilder(out, this, 0);
    	return out.toString();
    }
    
    /**
     * Returns true if this is the root branch.
     * @return true if root; false otherwise
     */
    public boolean isRoot() {
        return (ConfigurationManager.ROOT_BRANCH_NAME.equals(this.getName()));
    }

    /**
     * Returns a leaf that directly belongs to this branch.  It does not check sub-branches.
     * 
     * @return an <code>IConfigurationLeaf</code> that is an immediate leaf of this branch
     */
    private IConfigurationLeaf getChildLeaf(String name) throws IllegalArgumentException {
        IConfigurationLeaf leaf = (IConfigurationLeaf) leafMap.get(name);
        if (leaf==null) {
            throw new IllegalArgumentException( "No leaf named '" + name + "' on branch: " + this );
}
        return leaf;
    }

}
